﻿using System;
using System.Threading;

namespace CSConsoleTest
{
    class TestThread : BaseConsoleTest
    {
        private readonly object lockObj = new object();

        public void printDescription()
        {
            Console.WriteLine("--- Thread ---");
        }

        public void doTest()
        {
            ThreadWorks thread1Work = new ThreadWorks(lockObj) { ThreadName = "Worker-1" };
            ThreadWorks thread2Work = new ThreadWorks(lockObj) { ThreadName = "Worker-2" };
            Thread worker1 = new Thread(new ThreadStart(thread1Work.normalWork));
            Thread worker2 = new Thread(new ThreadStart(thread2Work.bigWork));

            PCPattern producer = new PCPattern(lockObj) { ThreadName = "Producer" };
            PCPattern consumer = new PCPattern(lockObj) { ThreadName = "Consumer" };
            Thread worker3 = new Thread(new ThreadStart(producer.produce));
            Thread worker4 = new Thread(new ThreadStart(consumer.consume));
            
            worker1.Start();
            worker2.Start();

            //worker1.Abort();
            //worker1.Interrupt();

            worker1.Join();
            worker2.Join();

            worker3.Start();
            worker4.Start();
        }
    }

    class ThreadWorks
    {
        private readonly object mLockObj;
        public string ThreadName { set; get; }

        public ThreadWorks(object aLockObj) 
        {
            mLockObj = aLockObj;
        }

        //Not thread-safe
        public void normalWork()
        {
            const int MAX_COUNT = 3;

            try
            {
                for(int job = 0; job < MAX_COUNT; job++)
                {
                    Console.WriteLine("{0} is working...{1}/{2}", ThreadName, job + 1, MAX_COUNT);
                    Thread.Sleep(500);
                }

                bigWork();
            }
            catch (ThreadInterruptedException tie)
            {
                Console.WriteLine(tie.StackTrace);
            }
            catch (ThreadAbortException tae)
            {
                Console.WriteLine(tae.StackTrace);
                Thread.ResetAbort();
            }
        }

        //Thread-safe
        public void bigWork()
        {
            try
            {
                lock (mLockObj)
                {
                    Console.WriteLine("{0} is entering to big work", ThreadName);
                    Thread.Sleep(5000);
                    Console.WriteLine("{0} is leaving from big work", ThreadName);
                }
            }
            catch (ThreadInterruptedException tie)
            {
                Console.WriteLine(tie.StackTrace);
            }
            catch (ThreadAbortException tae)
            {
                Console.WriteLine(tae.StackTrace);
                Thread.ResetAbort();
            }
        }
    }

    class PCPattern
    {
        public string ThreadName { set; get; }
        private readonly object mLockObj;
        private string mBread = null;

        public PCPattern(object aLockObj)
        {
            mLockObj = aLockObj;
        }

        public void produce()
        {
            try
            {
                Monitor.Enter(mLockObj);

                Console.WriteLine("{0} is making bread...", ThreadName);
                Thread.Sleep(3000);
                mBread = "Banana bread";
                Console.WriteLine("{0} has made bread.", ThreadName);

                Monitor.Pulse(mLockObj);
            }
            finally
            {
                Monitor.Exit(mLockObj);
            }
        }

        public void consume()
        {
            try
            {
                Monitor.Enter(mLockObj);

                if (mBread == null)
                {
                    Console.WriteLine("{0} is waiting for bread", ThreadName);
                    Monitor.Wait(mLockObj);
                }

                Console.WriteLine("{0} receive the bread {1}",ThreadName, mBread);
            }
            finally
            {
                Monitor.Exit(mLockObj);
            }
        }
    }
}
